﻿module VirtualCity.Scripts.Layer2

open Casanova
open Casanova.Utilities
open Casanova.Core
open Casanova.Coroutines
open Casanova.Game
open Casanova.Input
open Casanova.Math
open Casanova.Drawing
open VirtualCity.City
open VirtualCity.CityDescription
open VirtualCity.Plan
open VirtualCity.PlanDescription
open VirtualCity.Scripts.Layer1
open Wintellect.PowerCollections


let inline (!) x = immediate_lookup x


let contains_all (items:List<string>) (inventory:List<ItemDescription>) =
  let rec remove_first (l,found) y =
    match l with
    | x :: xs ->
      if x.Name = y then
        xs,found
      else
        let xs,found = remove_first (xs,found) y
        x::xs,found
    | [] -> [],false
  List.fold remove_first (inventory,true) items |> snd


let remove_all (items:List<string>) (inventory:List<ItemDescription>) =
  let rec remove_first l y =
    match l with
    | x :: xs ->
      if x.Name = y then
        xs
      else
        x :: remove_first xs y
    | [] -> []
  List.fold remove_first inventory items
    

let layer2_preconditions_ok (city:CityDescription) (agent:AgentDescription) ((UseAndTravel(location,item,use_effect_index)):Layer2ActionDescription) = 
  let clock = city.Clock
  let use_effects = item.UseEffects.[use_effect_index]
  let time_ok = item.UseSlots |> Seq.exists (UseSlot.Contains clock.Time)
  let stats_ok = Stats.LessEqual(use_effects.StatsLost,agent.Stats)
  let items_ok = contains_all use_effects.ItemsLost agent.Inventory
  let money_ok = use_effects.MoneyLost <= agent.Money
  if time_ok && stats_ok && items_ok && money_ok then true
  else false


let layer2_simulate_action (city:CityDescription) (agent:AgentDescription) ((UseAndTravel(location,item,use_effect_index)):Layer2ActionDescription) = 
  let use_effects = item.UseEffects.[use_effect_index]
  let target_distance = Vector2.Distance(location, agent.Position)
  let travel_time_elapsed = 
//      if target_distance > 1000.0f<m> then target_distance / (25.0f<km/h> |> to_meters_per_second) 
    if target_distance > 0.0f<m> then 2.0f * target_distance / agent.MaxSpeed
    else 0.0f<s>
  let new_inventory =
    [
      yield! agent.Inventory |> remove_all use_effects.ItemsLost
      for i in use_effects.ItemsGained do
        yield !i
    ]
  let new_agent = 
    {
      agent
        with Position = location
             Stats = (agent.Stats + use_effects.StatsGained * 0.9f) - use_effects.StatsLost * (1.1f * agent.Stats.StressHandicap)
             Money = agent.Money + use_effects.MoneyGained - use_effects.MoneyLost
             Inventory = new_inventory                  
    }
  let time_elapsed = use_effects.TimeSpent
  let updated_city,new_agent = update_city time_elapsed city Stats<1>.Zero new_agent
  let updated_city,new_agent = update_city travel_time_elapsed updated_city Stats<1>.One new_agent
  { Agent = new_agent; City = updated_city }


let useable_items (city:CityDescription) (agent:AgentDescription) =
  [
    for items,b in city.Buildings do
      if (Vector2.Distance(agent.Work.Position, b.Position) < 0.1f<m> || 
          Vector2.Distance(agent.Home.Position, b.Position) < 0.1f<m>) then
        for i in items do
          for use_effect_index in i.UseEffects |> Seq.mapi (fun i x -> i) do
            yield UseAndTravel(b.Position,i,use_effect_index)
    for items,b in city.Buildings do
      if Vector2.Distance(agent.Work.Position, b.Position) > 0.1f<m> &&
         Vector2.Distance(agent.Home.Position, b.Position) > 0.1f<m> && 
         Vector2.Distance(agent.Position, b.Position) < 2000.0f<m> then
        for i in items do
          if i.OpenUse then
            for use_effect_index in i.UseEffects |> Seq.mapi (fun i x -> i) do
              yield UseAndTravel(b.Position,i,use_effect_index)
  ]


let layer2_is_acceptable (plan:PlanDescription<Layer2ActionDescription>) =
  let last_step = plan.Steps.Head
  let agent = last_step.Expectations.Agent
  let stats_ok = stats_acceptable plan.Steps.Last.Expectations.Agent.Stats agent.Stats
  stats_ok


let layer2_reaches_goal (plan:PlanDescription<Layer2ActionDescription>) =
  let last_step = plan.Steps.Head
  let city = last_step.Expectations.City
  let agent = last_step.Expectations.Agent
  let plan_length_ok = plan.Steps.Length >= 10
  let stats_ok = stats_acceptable plan.Steps.Last.Expectations.Agent.Stats agent.Stats
  //let money_ok = agent.Money >= 50.0f<eur>
  plan_length_ok && stats_ok // money_ok && 


let layer2_compare_plans (p1:PlanDescription<Layer2ActionDescription>) (p2:PlanDescription<Layer2ActionDescription>) =
  let w1n = (p1.Steps |> List.head).Expectations
  let w2n = (p2.Steps |> List.head).Expectations
  let w10 = (p1.Steps.Last).Expectations
  let w20 = (p2.Steps.Last).Expectations
  let vital_stats_score1 = w1n.Agent.Stats.VitalMin - w10.Agent.Stats.VitalMin
  let vital_stats_score2 = w2n.Agent.Stats.VitalMin - w20.Agent.Stats.VitalMin
  let non_vital_stats_score1 = w1n.Agent.Stats.NonVitalMin - w10.Agent.Stats.NonVitalMin
  let non_vital_stats_score2 = w2n.Agent.Stats.NonVitalMin - w20.Agent.Stats.NonVitalMin
  let earnings1 = (w1n.Agent.Money - w10.Agent.Money) / (w10.Agent.Money + 1.0f<eur>)
  let earnings2 = (w2n.Agent.Money - w20.Agent.Money) / (w20.Agent.Money + 1.0f<eur>)
  let inventory_increase new_inventory old_inventory = 
    ((List.length new_inventory - List.length old_inventory) |> float32) / ((old_inventory |> List.length |> float32) + 1.0f)
  let items_acquired1 = inventory_increase w1n.Agent.Inventory w10.Agent.Inventory
  let items_acquired2 = inventory_increase w2n.Agent.Inventory w20.Agent.Inventory
  let score1 = vital_stats_score1 + w10.Agent.Stats.VitalMin * (non_vital_stats_score1 + (earnings1 + items_acquired1 * 0.1f) * w10.Agent.Stats.NonVitalMin)
  let score2 = vital_stats_score2 + w20.Agent.Stats.VitalMin * (non_vital_stats_score2 + (earnings2 + items_acquired2 * 0.1f) * w20.Agent.Stats.NonVitalMin)
  if score1 > score2 then 1
  elif score1 < score2 then -1
  else 0
